package com.hhsjdesign.dynamicui.asm.v2

import com.hhsjdesign.dynamicui.asm.Logger
import org.apache.commons.io.IOUtils
import org.objectweb.asm.*

import java.util.jar.JarEntry
import java.util.jar.JarFile
import java.util.jar.JarOutputStream
import java.util.zip.ZipEntry

/**
 * asm核心注入代码工具类
 */
class RegisterCodeGeneratorV2 {
    ScanSettingV2 extension

    private RegisterCodeGeneratorV2(ScanSettingV2 extension) {
        this.extension = extension
    }

    static void insertInitCodeTo(ScanSettingV2 registerSetting) {
        if (registerSetting != null && !registerSetting.classList.isEmpty()) {
            RegisterCodeGeneratorV2 processor = new RegisterCodeGeneratorV2(registerSetting)
            File file = DynamicUiTransformV2.targetAsmClass
            if (file.getName().endsWith('.jar'))
                processor.insertInitCodeIntoJarFile(file)
        }
    }

    /**
     * generate code into jar file
     * @param jarFile the jar file which contains LogisticsCenter.class
     * @return
     */
    private File insertInitCodeIntoJarFile(File jarFile) {
        if (jarFile) {
            def optJar = new File(jarFile.getParent(), jarFile.name + ".opt")
            if (optJar.exists())
                optJar.delete()
            def file = new JarFile(jarFile)
            Enumeration enumeration = file.entries()
            JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(optJar))

            while (enumeration.hasMoreElements()) {
                JarEntry jarEntry = (JarEntry) enumeration.nextElement()
                String entryName = jarEntry.getName()
                ZipEntry zipEntry = new ZipEntry(entryName)
                InputStream inputStream = file.getInputStream(jarEntry)
                jarOutputStream.putNextEntry(zipEntry)
                //找到指定的类文件，就执行字节码操作
                if (ScanSettingV2.GENERATE_TO_CLASS_FILE_NAME == entryName) {

                    Logger.i('Insert init code to class >> ' + entryName)

                    //真正的插入
                    def bytes = asmOperate(inputStream)
                    jarOutputStream.write(bytes)
                } else {
                    jarOutputStream.write(IOUtils.toByteArray(inputStream))
                }
                inputStream.close()
                jarOutputStream.closeEntry()
            }
            jarOutputStream.close()
            file.close()

            if (jarFile.exists()) {
                jarFile.delete()
            }
            optJar.renameTo(jarFile)
        }
        return jarFile
    }

    /**
     * 字节码访问和操作
     * @param inputStream
     * @return
     */
    private byte[] asmOperate(InputStream inputStream) {
        //首先那是一个类，读到ClassReader中
        ClassReader cr = new ClassReader(inputStream)
        //然后用ClassWriter修改
        ClassWriter cw = new ClassWriter(cr, 0)
        //类访问器
        ClassVisitor cv = new MyClassVisitor(Opcodes.ASM6, cw)
        //然后执行，用reader接收writer到操作，
        cr.accept(cv, ClassReader.SKIP_FRAMES)
        //最终砖层字节数组
        return cw.toByteArray()
    }


    /**
     * 首先是类的访问
     */
    class MyClassVisitor extends ClassVisitor {

        MyClassVisitor(int api, ClassVisitor cv) {
            super(api, cv)
        }

        void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
            super.visit(version, access, name, signature, superName, interfaces)
        }

        //访问方法，首先访问方法
        @Override
        MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
            MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions)
            //generate code into this method
            //访问我执行到方法。
            if (name == ScanSettingV2.GENERATE_TO_METHOD_NAME1) {
                mv = new DynamicUiMethodVisitorJava(Opcodes.ASM6, mv, ScanSettingV2.GENERATE_TO_METHOD_NAME1)
            }
            if (name == ScanSettingV2.GENERATE_TO_METHOD_NAME2) {
                mv = new DynamicUiMethodVisitorJava(Opcodes.ASM6, mv, ScanSettingV2.GENERATE_TO_METHOD_NAME2)
            }
            if (name == ScanSettingV2.GENERATE_TO_METHOD_NAME3) {
                mv = new DynamicUiMethodVisitorJava(Opcodes.ASM6, mv, ScanSettingV2.GENERATE_TO_METHOD_NAME3)
            }
            if (name == ScanSettingV2.GENERATE_TO_METHOD_NAME4) {
                mv = new DynamicUiMethodVisitorJava(Opcodes.ASM6, mv, ScanSettingV2.GENERATE_TO_METHOD_NAME4)
            }
            if (name == ScanSettingV2.GENERATE_TO_METHOD_NAME5) {
                mv = new DynamicUiMethodVisitorJava(Opcodes.ASM6, mv, ScanSettingV2.GENERATE_TO_METHOD_NAME5)
            }
            if (name == ScanSettingV2.GENERATE_TO_METHOD_NAME6) {
                mv = new DynamicUiMethodVisitorJava(Opcodes.ASM6, mv, ScanSettingV2.GENERATE_TO_METHOD_NAME6)
            }
            if (name == ScanSettingV2.GENERATE_TO_METHOD_NAME7) {
                mv = new DynamicUiMethodVisitorJava(Opcodes.ASM6, mv, ScanSettingV2.GENERATE_TO_METHOD_NAME7)
            }
            return mv
        }
    }

    //方法访问者
    class DynamicUiMethodVisitorJava extends MethodVisitor {

        static int lines = 14
        String methodName;

        DynamicUiMethodVisitorJava(int api, MethodVisitor mv, String methodName) {
            super(api, mv)
            this.methodName = methodName
        }

        void asm(ScanSettingV2.AnnoValue anno) {
            String className = anno.className
            println "类名： ${className}"
            Label label1 = new Label();
            mv.visitLabel(label1);
            mv.visitLineNumber(lines, label1);
            mv.visitVarInsn(Opcodes.ALOAD, 1);
            mv.visitLdcInsn(anno.annoValue)
            mv.visitLdcInsn(anno.className)
            //获取map，调用map.put方法
//                        mv.visitFieldInsn(Opcodes.GETSTATIC, name, "INSTANCE", "L${name};");
            mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/Map",
                    "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", true);
            mv.visitInsn(Opcodes.POP);
            lines++;
        }

        @Override
        void visitInsn(int opcode) {
            //generate code before return
            if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)) {
                if (methodName == ScanSettingV2.GENERATE_TO_METHOD_NAME1) {
                    //获取到第1个参数，就是map
                    for (anno in extension.annoValueSet) {
                        if (anno.annoType != "WIDGET") {
                            continue
                        }
                        asm(anno)
//                        String className = anno.className
//                        println "类名： ${className}"
//                        Label label1 = new Label();
//                        mv.visitLabel(label1);
//                        mv.visitLineNumber(line, label1);
//                        mv.visitLdcInsn(anno.annoValue)
//                        mv.visitLdcInsn(anno.className)
//                        //获取map，调用map.put方法
////                        mv.visitFieldInsn(Opcodes.GETSTATIC, name, "INSTANCE", "L${name};");
//                        mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/Map",
//                                "put", "(Ljava/lang/String;Ljava/lang/String;)V", true);
//                        line++;
                    }
                }

                if (methodName == ScanSettingV2.GENERATE_TO_METHOD_NAME2) {
                    lines = lines + 4
                    for (anno in extension.annoValueSet) {
                        if (anno.annoType != "BUSINESS_BEAN") continue
                        asm(anno)
                    }
                }

                if (methodName == ScanSettingV2.GENERATE_TO_METHOD_NAME3) {
                    lines = lines + 4
                    for (anno in extension.annoValueSet) {
                        if (anno.annoType != "WIDGET_DATA") continue
                        asm(anno)
                    }
                }

                if (methodName == ScanSettingV2.GENERATE_TO_METHOD_NAME4) {
                    lines = lines + 4
                    for (anno in extension.annoValueSet) {
                        if (anno.annoType != "WIDGET_PROPERTY") continue
                        asm(anno)
                    }
                }

                if (methodName == ScanSettingV2.GENERATE_TO_METHOD_NAME5) {
                    lines = lines + 4
                    for (anno in extension.annoValueSet) {
                        if (anno.annoType != "VIEW_HOLDER") continue
                        asm(anno)
                    }
                }

                if (methodName == ScanSettingV2.GENERATE_TO_METHOD_NAME6) {
                    lines = lines + 4
                    for (anno in extension.annoValueSet) {
                        if (anno.annoType != "BUSINESS_CALLER") continue
                        asm(anno)
                    }
                }

                if (methodName == ScanSettingV2.GENERATE_TO_METHOD_NAME7) {
                    lines = lines + 4
                    for (anno in extension.annoValueSet) {
                        if (anno.annoType != "WIDGET_ITEM_DATA") continue
                        asm(anno)
                    }
                }
            }
            super.visitInsn(opcode)
        }

        @Override
        void visitMaxs(int maxStack, int maxLocals) {
            super.visitMaxs(maxStack + 3, maxLocals)
        }

        @Override
        void visitEnd() {
            super.visitEnd()
        }
    }
}